package com.zeyu.framework.tools.report.convert.server;

import com.google.common.collect.Lists;
import com.zeyu.framework.tools.report.convert.ConverterException;
import org.apache.commons.io.IOUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.annotation.PreDestroy;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.net.SocketTimeoutException;
import java.net.URL;
import java.net.URLConnection;
import java.util.ArrayList;
import java.util.Timer;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeoutException;

/**
 * 图片转换服务,调用library进程
 * Created by zeyuphoenix on 16/9/3.
 */
public class Server {

    // ================================================================
    // Constants
    // ================================================================

    /**
     * logger
     */
    private static final Logger logger = LoggerFactory.getLogger(Server.class);

    // ================================================================
    // Fields
    // ================================================================

    // 转换进程
    private Process process;
    // 基本参数
    private final int port;
    private final String host;
    private final int readTimeout;
    private final int connectTimeout;
    private final int maxTimeout;
    // 服务状态
    private ServerState state = ServerState.IDLE;

    // 打印标志
    private boolean printable = true;

    // ================================================================
    // Constructors
    // ================================================================

    /**
     * 构造函数
     * @param exec 执行cmd
     * @param script 执行script
     * @param host 服务地址
     * @param port 服务端口
     * @param connectTimeout 超时时间
     * @param readTimeout 读超时
     * @param maxTimeout 最大等待
     */
    public Server(String exec, String script, String host, int port,
                  int connectTimeout, int readTimeout, int maxTimeout) {

        // assign port and host to this instance
        this.port = port;
        this.host = host;
        this.connectTimeout = connectTimeout;
        this.readTimeout = readTimeout;
        this.maxTimeout = maxTimeout;

        try {
            ArrayList<String> commands = Lists.newArrayList();
            commands.add(exec);
            commands.add(script);
            commands.add("-host");
            commands.add(host);
            commands.add("-port");
            commands.add("" + port);

            logger.info("Server command is: {}", commands.toString());

            process = new ProcessBuilder(commands).start();
            final BufferedReader bufferedReader = new BufferedReader(
                    new InputStreamReader(process.getInputStream()));
            String readLine = bufferedReader.readLine();
            if (readLine != null) {
                logger.debug("Console info from script: phantomjs#> {}", readLine);
            }

            Executors.newSingleThreadExecutor().execute(() -> {
                while (printable) {
                    try {
                        final BufferedReader bufferedReader1 = new BufferedReader(
                                new InputStreamReader(process
                                        .getInputStream()));
                        String readLine1 = bufferedReader1.readLine();
                        if (readLine1 != null) {
                            logger.debug("Thread -- Console info from script: phantomjs#> "
                                    + readLine1);
                        }
                        // Thread.sleep(2);
                    } catch (Exception ignored) {
                    }
                }

            });

            // 有错误验证
            /**
             * final BufferedReader errorbufferedReader = new BufferedReader(
             * new InputStreamReader(process.getErrorStream())); String
             * errorInfo = errorbufferedReader.readLine(); if (errorInfo !=
             * null) { logger.info("Error info from script: " + errorInfo); }
             */

            if (readLine == null || !readLine.contains("ready")) {
                throw new RuntimeException("Error, PhantomJS could not start");
            }

            initialize();

            Runtime.getRuntime().addShutdownHook(new Thread() {
                @Override
                public void run() {
                    printable = false;
                    if (process != null) {
                        logger.warn("Shutting down PhantomJS instance, kill process directly, "
                                + this.toString());
                        try {
                            process.getErrorStream().close();
                            process.getInputStream().close();
                            process.getOutputStream().close();
                        } catch (IOException e) {
                            logger.warn("Error while shutting down process: " + e.getMessage());
                        }
                        process.destroy();
                    }
                }
            });
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

    // ================================================================
    // Methods from/for super Interfaces or SuperClass
    // ================================================================

    @Override
    public String toString() {
        return this.getClass().getName() + "listening to port: " + port;
    }

    // ================================================================
    // Public or Protected Methods
    // ================================================================

    public void initialize() {
        logger.info("Phantom server started on port " + port);
    }

    public String request(String params) throws SocketTimeoutException,
            ConverterException, TimeoutException {
        String response;
        Timer _timer = new Timer();
        try {
            URL url = new URL("http://" + host + ":" + port + "/");

            // TEST with running a local phantom instance
            // url = new URL("http://" + host + ":7777/");
            // logger.log(Level.INFO, "requesting url: " + url.toString());
            // logger.log(Level.INFO, "parameters: " + params);

            state = ServerState.BUSY;

            _timer.schedule(new TimeOut(this), maxTimeout);

            URLConnection connection = url.openConnection();
            connection.setDoOutput(true);
            connection.setConnectTimeout(connectTimeout);
            connection.setReadTimeout(readTimeout);

            OutputStream out = connection.getOutputStream();
            out.write(params.getBytes("utf-8"));
            out.close();
            InputStream in = connection.getInputStream();
            response = IOUtils.toString(in, "utf-8");

            in.close();
            _timer.cancel();
            state = ServerState.IDLE;
        } catch (SocketTimeoutException ste) {
            _timer.cancel();
            throw new SocketTimeoutException(ste.getMessage());
        } catch (Exception e) {
            logger.error("", e);
            if (state == ServerState.TIMEDOUT) {
                throw new TimeoutException(e.getMessage());
            }
            _timer.cancel();
            throw new ConverterException(e.getMessage());
        }
        return response;
    }

    public void cleanup() {
        try {
            /* It's not enough to only destroy the process, this helps */
            process.getErrorStream().close();
            process.getInputStream().close();
            process.getOutputStream().close();
        } catch (IOException e) {
            logger.error("Error while shutting down process: ", e);
        }

        process.destroy();
        process = null;
        logger.info("Destroyed phantomJS process running on port " + port);
    }

    public int getPort() {
        return port;
    }

    public String getHost() {
        return host;
    }

    public ServerState getState() {
        return state;
    }

    public void setState(ServerState state) {
        this.state = state;
    }

    // 销毁前关闭process
    @PreDestroy
    public void destroy() {
        printable = false;
        if (process != null) {
            logger.warn("Shutting down PhantomJS instance, kill process directly, {}", this.toString());
            try {
                process.getErrorStream().close();
                process.getInputStream().close();
                process.getOutputStream().close();
            } catch (IOException e) {
                logger.warn("Error while shutting down process: {}", e.getMessage());
            }
            process.destroy();
        }
    }

    // ================================================================
    // Getter & Setter
    // ================================================================

    // ================================================================
    // Private Methods
    // ================================================================

    // ================================================================
    // Inner or Anonymous Class
    // ================================================================

    // ================================================================
    // Test Methods
    // ================================================================

}
